package table;

import java.util.Random;

import log.Logger;
import log.NullLogger;

import philosoph.*;
import user.util.GenericArray;
import warden.*;

public class RoundWalkingTable <T extends Philosoph> implements Table<T> {

	private GenericArray<T> seats;
	private Object[] sync;
	private Object waitSync = new Object();
	private EatTimeWarden<T> warden;
	private EatAmountWarden<T> amountWarden;
	private Logger logger = new NullLogger();

	public RoundWalkingTable(int seats, EatTimeWarden<T> eatTimeWarden, EatAmountWarden<T> eatAmountWarden) {
		this(seats, eatTimeWarden, eatAmountWarden, new NullLogger());
	}

	public RoundWalkingTable(int seats, EatTimeWarden<T> eatTimeWarden, EatAmountWarden<T> eatAmountWarden, Logger logger) {
		this.logger = logger;
		this.warden = eatTimeWarden;
		this.amountWarden = eatAmountWarden;
		if (seats < 2) throw new IllegalArgumentException();
		this.seats = new GenericArray<T>(seats >> 1);
		this.sync = new Object[seats >> 1];
		for (int i = 0; i < sync.length; i++) {
			sync[i] = new Object();
		}
	}

	public void sitNEat(final T philosoph) throws InterruptedException {
		this.sitNEat(philosoph, new Random().nextInt(this.sync.length));
	}

	public void sitNEat(final T philosoph, final int seat) throws InterruptedException {
		this.amountWarden.blockPhilosoph(philosoph);
		this.logger.log(-1, "Philosoph Nr. " + String.format("%4d", philosoph.hashCode()) + " is looking for a seat.");
		int seatNumber = this.getSeat(philosoph, seat);
		this.logger.log(-1, "Philosoph Nr. " + String.format("%4d", philosoph.hashCode()) + " got seat number " + String.format("%4d", seatNumber << 1) + ".");
		try {
			this.eat(philosoph);
			this.release(seatNumber);
			this.logger.log(-1, "Philosoph Nr. " + String.format("%4d", philosoph.hashCode()) + " releases seat number " + String.format("%4d", seatNumber<< 1) + ".");
		} catch (InterruptedException e) {
			this.release(seatNumber);
			this.logger.log(-1, "Philosoph Nr. " + String.format("%4d", philosoph.hashCode()) + " releases seat number " + String.format("%4d", seatNumber << 1) + ".");
			throw e;
		}
	}

	private void release(int seatNumber) {
		synchronized (this.sync[seatNumber]) {
			this.seats.set(seatNumber, null);
		}
		synchronized (this.waitSync) {
			this.waitSync.notifyAll();
		}
	}

	private int getSeat(T philosoph, int seat) throws InterruptedException{
		while (true) { 
			for (int i = 0, j = seat; i < this.sync.length ; i++, j++) {
				if (j >= this.sync.length) j = 0;
				synchronized (this.sync[j]) {
					if (this.seats.get(j) == null) {
						this.seats.set(j, philosoph);
						return j;
					}
				}
			}
			synchronized (waitSync) {
				this.waitSync.wait();
			}
		}
	}

	private void eat(final T philosoph) throws InterruptedException {
		new Thread(new Runnable() {

			@Override
			public void run() {
				RoundWalkingTable.this.amountWarden.incrementPhilosoph(philosoph);
			}
		}).start();
		this.warden.watchOver(philosoph);
	}

}
